(* ::Package:: *)

(* 
	Copyright (C) 2009 Hans-Martin Will.

	This software is licensed under the Creative Commons Attribution 3.0 License
	http://creativecommons.org/licenses/by/3.0/.
	Distributions of this software in modified or unmodified form need to 
	include this notice.
*)

BeginPackage["Bioinformatica`"];

ReadSOFT::"usage" = "ReadSOFT[filename] reads a file in NCBI SOFT file format \
into memory. The returned object is the head of a set of rules index by attribute \
name and entity ID.";

Begin["`Private`"];

(* 
	Meta-Data declaring the different SOFT attribute types
	
	Each tag is mapped to a quintuple with the following entries:
	- the symbol to use to represent the attribute value
	- a Boolean flag indicating if the attribute is a list value
	- a Boolean flag indicating if the attribute is an indexed value
	- the index value for indexed attribute
	- a function symbol head of a handler function to call for attributes 
		requiring special treatment or Null
*)

attributes["PLATFORM"] :=
{ 
"Platform_title" -> {"platformTitle", False, False, 0, Null},
"Platform_distribution" ->{"platformDistribution", False, False, 0, Null},
"Platform_technology" -> {"platformTechnology", False, False, 0, Null},
"Platform_organism" -> {"platformOrganism", True, False, 0, Null},
"Platform_manufacturer" -> {"platformManufacturer", False, False, 0, Null},
"Platform_manufacture_protocol" -> {"platformManufactureProtocol", False, False, 0, Null},
"Platform_catalog_number" -> {"platformCatalogNumber", True, False, 0, Null},
"Platform_web_link" -> {"platformWebLink", True, False, 0, Null},
"Platform_support" -> {"platformSupport", False, False, 0, Null},
"Platform_coating" -> {"platformCoating", False, False, 0, Null},
"Platform_description" -> {"platformDescription", True, False, 0, Null},
"Platform_contributor" -> {"platformContributor", True, False, 0, Null},
"Platform_pubmed_id" -> {"platformPubmedId", True, False, 0, Null},
"Platform_geo_accession" -> {"platformGeoAccession", True, False, 0, Null},
"Platform_table_begin" -> {"platformTableBegin", False, False, 0, platformTableBegin},
"Platform_table_end" -> {"platformTableEnd", False, False, 0, platformTableEnd}
};

attributes["SAMPLE"] :=
{
"Sample_title" -> {"sampleTitle", False, False, 0, Null},
"Sample_supplementary_file" -> {"sampleSupplementaryFile", True, False, 0, Null},
"Sample_table" -> {"sampleTable", False, False, 0, Null},
"Sample_source_name_ch" ~~ n:NumberString -> {"sampleSourceNameCh", False, True, n, Null},
"Sample_organism_ch" ~~ n:NumberString-> {"sampleOrganismCh", True, True, n, Null},
"Sample_characteristics_ch" ~~ n:NumberString -> {"sampleCharacteristicsCh",  True, True, n, Null},
"Sample_biomaterial_provider_ch" ~~ n:NumberString -> {"sampleBiomaterialProviderCh",  True, True, n, Null},
"Sample_treatment_protocol_ch" ~~ n:NumberString -> {"sampleTreatmentProtocolCh", True, True, n, Null},
"Sample_growth_protocol_ch" ~~ n:NumberString -> {"sampleGrowthProtocolCh", True, True, n, Null},
"Sample_molecule_ch" ~~ n:NumberString -> {"sampleMoleculeCh", False, True, n,  Null},
"Sample_extract_protocol_ch" ~~ n:NumberString -> {"sampleExtractProtocolCh", True, True, n, Null},
"Sample_label_ch" ~~ n:NumberString -> {"sampleLabelCh", False, True, n, Null },
"Sample_label_protocol_ch" ~~ n:NumberString -> {"sampleLabelProtocolCh", True, True, n, Null},
"Sample_hyb_protocol" -> {"sampleHybProtocol", True, False, 0, Null},
"Sample_scan_protocol" -> {"sampleScanProtocol", True, False, 0, Null},
"Sample_data_processing" -> {"sampleDataProcessing", False, False, 0, Null},
"Sample_description" -> {"sampleDescription", True, False, 0, Null},
"Sample_platform_id" -> {"samplePlatformId", False, False, 0, Null},
"Sample_geo_accession" -> {"sampleGeoAccession", False, False, 0, Null},
"Sample_anchor" -> {"sampleAnchor", False, False, 0, Null},
"Sample_type" -> {"sampleType", False, False, 0, Null},
"Sample_tag_count" -> {"sampleTagCount", False, False, 0, Null},
"Sample_tag_length" -> {"sampleTagLength", False, False, 0, Null},
"Sample_table_begin" -> {"sampleTableBegin", False, False, 0, sampleTableBegin},
"Sample_table_end" -> {"sampleTableEnd", False, False, 0, sampleTableEnd}
};

attributes["SERIES"] :=
{
"Series_title" -> { "seriesTitle", False, False, 0, Null},
"Series_summary" -> { "seriesSummary", True, False, 0, Null},
"Series_overall_design" -> { "seriesOverallDesign", False, False, 0, Null},
"Series_pubmed_id" -> { "seriesPubmedId", True, False, 0, Null},
"Series_web_link" -> { "seriesWebLink", True, False, 0, Null},
"Series_contributor" -> { "seriesContributor", True, False, 0, Null},
"Series_variable_" ~~ n:NumberString -> { "seriesVariable", True, True, n, Null},
"Series_variable_description_" ~~ n:NumberString -> { "seriesVariableDescription", True, True, n, Null},
"Series_variable_sample_list_" ~~ n:NumberString -> { "seriesVariableSampleList", True, True, n, Null},
"Series_repeats_" ~~ n:NumberString -> { "seriesRepeats", True, True, n, Null},
"Series_repeats_sample_list_" ~~n: NumberString -> { "seriesRepeatsSampleList", True, True, n, Null},
"Series_sample_id" -> { "seriesSampleId", True, False, 0, Null},
"Series_geo_accession" -> { "seriesSGeoAccession", False, False, 0, Null}
};

attributes["DATABASE"] :=
{ 
"Database_name" -> { "databaseName", False, False, 0, Null},
"Database_institute" -> { "databaseInstitute", True, False, 0, Null},
"Database_web_link" -> { "databaseWebLink", True, False, 0, Null},
"Database_email" -> { "databaseEmail", True, False, 0, Null},
"Database_ref" -> { "databaseRef", True, False, 0, Null}
};

attributes["DATASET"] :=
{ 
"dataset_title" -> { "datasetTitle", False, False, 0, Null},
"dataset_description" -> { "datasetDescription", True, False, 0, Null},
"dataset_type" -> { "datasetType", False, False, 0, Null},
"dataset_pubmed_id" -> { "datasetPubmedId", True, False, 0, Null},
"dataset_platform" -> { "datasetPlatform", False, False, 0, Null},
"dataset_platform_organism" -> { "datasetPlatformOrganism", True, False, 0, Null},
"dataset_platform_technology_type"-> { "datasetPlatformTechnologyType", False, False, 0, Null},
"dataset_feature_count" -> { "datasetFeatureCount", False, False, 0, Null},
"dataset_sample_organism" -> { "datasetSampleOrganism", True, False, 0, Null},
"dataset_sample_type" -> { "datasetSampleType", True, False, 0, Null},
"dataset_channel_count" -> { "datasetChannelCount", False, False, 0, Null},
"dataset_sample_count" -> { "datasetSampleCount", False, False, 0, Null},
"dataset_value_type" -> { "datasetValueType", False, False, 0, Null},
"dataset_reference_series" -> { "datasetReferenceSeries", False, False, 0, Null},
"dataset_order" -> { "datasetOrder", False, False, 0, Null},
"dataset_update_date" -> { "datasetUpdateDate", False, False, 0, Null},
"dataset_table_begin" -> { "datasetTableBegin", False, False, 0, datasetTableBegin},
"dataset_table_end" -> { "datasetTableEnd", False, False, 0, datasetTableEnd}
};

attributes["SUBSET"] :=
{ 
"subset_dataset_id"-> { "subsetDatasetId", False, False, 0, Null},
"subset_description"-> { "subsetDescription", True, False, 0, Null},
"subset_sample_id"-> { "subsetSampleId", True, False, 0, Null},
"subset_type"-> { "subsetType", True, False, 0, Null}
};

(*
	Parser helper functions
*)

keyValue[line_] := StringTrim @ StringSplit[StringDrop[line, 1], "="];

readEntity[self_, line_] := Module[{key, value, oldValue},
   {key, value} = keyValue[line]; 
	oldValue = Evaluate[self[key]];
	If[Head[oldValue] === self, Set[self[key], {value}], Set[self[key], Union[oldValue, { value }]]];
   {value, key, {}, {}}
   ];

readAttribute[self_, state_, line_] := Module[
   {key, value = "", entityId, entityType, columns, table, 
    attribute, head, isList, isIndex, index, func,
    oldValue, newValue},
   
   value = keyValue[line];
   If [Length[value] == 2,
    {key, value} = value,
    key = StringTrim [StringDrop[line, 1]]; value = ""];
   
   {entityId, entityType, columns, table} = state;
   attribute = key /. attributes[entityType];
   If [MatchQ[attribute, {_, _, _, _, _}],
    {head, isList, isIndex, index, func} = attribute;
    (*Print[head, " ", isList, " ", isIndex, " ", index, " ", func];*)

        If[func =!= Null,
     Return[func[self, state, line]],
     If[isIndex,
      If[isList, 
       oldValue = self[head, entityId, index];
       If [Head[oldValue] == self, 
        Set[self[head, entityId, index], value],
        Set[self[head, entityId, index], 
         Append[If[Head[oldValue] =!= List, {oldValue}, oldValue], 
          value]]
        ],
       Set[self[head, entityId, index], value]],
      If[isList, 
       oldValue = self[head, entityId];
       If [Head[oldValue] == self, 
        Set[self[head, entityId], value],
        Set[self[head, entityId], 
         Append[If[Head[oldValue] =!= List, {oldValue}, oldValue], 
          value]]],
       Set[self[head, entityId], value]]]]
    ];
   state
   ];

convertHeader[value_] :=
	Symbol[StringReplace[value, {"_" -> "U", "." -> "`"}]];

readColumn[self_, state_, line_] := Module[
   {
    key, value, entityId, entityType, columns, table, rule
    },
   {key, value} = keyValue[line]; 
   {entityId, entityType, columns, table} = state;
   columns = Append[columns, Rule[convertHeader[key], value]];
   {entityId, entityType, columns, table}
   ];

readTableHeaders[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table,
	row = convertHeader /@ StringSplit[line]
    },
   {entityId, entityType, columns, table} = state;
   (* save the table and reset to an empty table *)
   
   (*Print[self[datasetTable, entityId]];*)
   self["datasetTableHeader", entityId] = row;
   state
   ];

convertValue[value_] := 
  With[{expr = ToExpression[value]}, 
   If[NumberQ[expr], N[expr], value]];

readTableRow[self_, state_, line_] := Module[
   (* Local variables *)
   {
    row = convertValue /@ StringSplit[line],
    entityId, entityType, columns, table, newTable
    }, 
   (* Body *)
   {entityId, entityType, columns, table} = state;
   If[Length[row] != Length[columns], 
    Print["Current row uses different number of columns than declared"]];
   (* update state *)
   
   newTable = If[Length[table] == 0, { row }, Append[table, row]];
   {entityId, entityType, columns, newTable}
   ];

(*
	Special attribute handler functions
*)

platformTableBegin[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table
    },
   {entityId, entityType, columns, table} = state;
   (* start an empty table *)
   {entityId, entityType, columns, {}}
   ];

platformTableEnd[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table
    },
   {entityId, entityType, columns, table} = state;
   (* save the table and reset to an empty table *)
   
   self["platformTable", entityId] = table;
   self["platformTableColumns", entityId] = columns;
   {entityId, entityType, {}, {}}
   ];

sampleTableBegin[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table
    },
   {entityId, entityType, columns, table} = state;
   (* start an empty table *)
   {entityId, entityType, columns, {}}
   ];

sampleTableEnd[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table
    },
   {entityId, entityType, columns, table} = state;
   (* save the table and reset to an empty table *)
   
   self["sampleTable", entityId] = table;
   self["sampleTableColumns", entityId] = columns;
   {entityId, entityType, {}, {}}
   ];

datasetTableBegin[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table
    },
   {entityId, entityType, columns, table} = state;
   (* start an empty table *)
   {entityId, entityType, columns, {}}
   ];

datasetTableEnd[self_, state_, line_] := Module[
   {
    entityId, entityType, columns, table
    },
   {entityId, entityType, columns, table} = state;
   (* save the table and reset to an empty table *)
   
   (*Print[self[datasetTable, entityId]];*)
   self["datasetTable", entityId] = table;
   self["datasetTableColumns", entityId] = columns;
   {entityId, entityType, {}, {}}
   ];

(*
	Exported Entry Point
*)

ReadSOFT[name_] := 
  Module[{handle, nextLine, count = 0, state, tableRow = False, 
    seenHeader = False, self},
   state = {"NONE", NONE, {}, {}};
   handle = OpenRead[name];
   nextLine = Read[handle, String];
   While[nextLine  =!= EndOfFile,
    (*Print[nextLine];*)
    Switch[StringTake[nextLine, 1],
     "^", state = readEntity[self, nextLine]; tableRow = False, 
     "!", state = readAttribute[self, state, nextLine]; 
     tableRow = False,
     "#", state = readColumn[self, state, nextLine]; 
     tableRow = False,
     _, state = 
      If[seenHeader, readTableRow[self, state, nextLine], 
       readTableHeaders[self, state, nextLine]]; tableRow = True
     ];
    nextLine = Read[handle, String];
    seenHeader = tableRow;
    count = count + 1];
   Close[handle];
   self
   ];

End[];
EndPackage[];



